package bsh;

import java.util.Collection;
import java.util.List;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.asm.acessor.ObjectAccessor;
import bsh.exception.CompilationFailedException;
import bsh.meta.AbstractTypeWrapper;
import bsh.meta.DeclaredConstructor;
import bsh.meta.FieldDescriptor;
import bsh.meta.MethodDescriptor;
import bsh.meta.Scope;
import bsh.meta.TypeWrapper;
import bsh.meta.UnfinishedInnerType;

/**
 * {@link UnfinishedInnerType} that delegates to a (lazily initialized) inner {@link UnfinishedInnerType}
 * 
 * @author takeshi
 */
public class LazyInitializingUnfinishedInnerType extends AbstractTypeWrapper implements UnfinishedInnerType {

    private UnfinishedInnerType delegate;

    private final ClassDeclarationNodeCompiler compiler;

    private final String sourceCodeName;

    private final String className;

    private final String simpleName;

    private final int modifiers;

    private final Scope declaringScope;

    /**
     * C'tor
     * 
     * @param declaringScope the declaring scope
     * @param sourceCodeName the name of the file that declares this type
     * @param className the class name
     * @param simpleName the simple name (or <code>null</code> if annonymous)
     * @param compiler the compiler that will (eventually) compile the inner class
     * @param modifiers the modifier
     */
    public LazyInitializingUnfinishedInnerType(Scope declaringScope, String sourceCodeName, String className, String simpleName,
            ClassDeclarationNodeCompiler compiler, int modifiers) {
        this.sourceCodeName = sourceCodeName;
        this.className = className;
        this.simpleName = simpleName;
        this.compiler = compiler;
        this.modifiers = modifiers;
        this.declaringScope = declaringScope;
    }

    /**
     * Ensures that the delegate is properly initialized
     */
    private synchronized void ensureDelegateInitialized() {
        if (delegate != null) {
            return;
        }
        try {
            this.delegate = compiler.compileClassDeclaration(declaringScope);
        } catch (CompilationFailedException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public List<String> externalVariables() {
        this.ensureDelegateInitialized();
        return delegate.externalVariables();
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor getEnclosingInstanceField() {
        this.ensureDelegateInitialized();
        return delegate.getEnclosingInstanceField();
    }

    /**
     * {@inheritDoc}
     */
    public void setExternalVariables(List<String> arrayList) {
        this.ensureDelegateInitialized();
        delegate.setExternalVariables(arrayList);
    }

    /**
     * {@inheritDoc}
     */
    public ObjectAccessor getDynamicVariablesFieldAccessor() {
        this.ensureDelegateInitialized();
        return delegate.getDynamicVariablesFieldAccessor();
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends List<TypeWrapper>> visibleConstructors() {
        this.ensureDelegateInitialized();
        return delegate.visibleConstructors();
    }

    /**
     * {@inheritDoc}
     */
    public ObjectAccessor getMathContextFieldAccessor() {
        this.ensureDelegateInitialized();
        return delegate.getMathContextFieldAccessor();
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor getDynamicsField() {
        this.ensureDelegateInitialized();
        return delegate.getDynamicsField();
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor getMathContextField() {
        this.ensureDelegateInitialized();
        return delegate.getMathContextField();
    }

    /**
     * {@inheritDoc}
     */
    public void addInnerType(UnfinishedInnerType innerType) {
        this.ensureDelegateInitialized();
        delegate.addInnerType(innerType);
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends TypeWrapper> getInnerTypes() {
        this.ensureDelegateInitialized();
        return delegate.getInnerTypes();
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper getEnclosingType() {
        return this.compiler.getUnit().thisType();
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends MethodDescriptor> getMethods() {
        this.ensureDelegateInitialized();
        return delegate.getMethods();
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends FieldDescriptor> visibleFields() {
        this.ensureDelegateInitialized();
        return delegate.visibleFields();
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addField(String name, TypeWrapper type) {
        this.ensureDelegateInitialized();
        return delegate.addField(name, type);
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends FieldDescriptor> protectedFields() {
        this.ensureDelegateInitialized();
        return delegate.protectedFields();
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addField(String name, TypeWrapper type, int mods) {
        this.ensureDelegateInitialized();
        return delegate.addField(name, type, mods);
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addStaticField(String name, TypeWrapper type, int options) {
        this.ensureDelegateInitialized();
        return delegate.addStaticField(name, type, options);
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends MethodDescriptor> visibleMethods() {
        this.ensureDelegateInitialized();
        return delegate.visibleMethods();
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addField(String name, Class<?> type) {
        this.ensureDelegateInitialized();
        return delegate.addField(name, type);
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends MethodDescriptor> protectedMethods() {
        this.ensureDelegateInitialized();
        return delegate.protectedMethods();
    }

    /**
     * {@inheritDoc}
     */
    public void addMethod(MethodDescriptor method) {
        this.ensureDelegateInitialized();
        delegate.addMethod(method);
    }

    /**
     * {@inheritDoc}
     */
    public void addConstructor(List<? extends TypeWrapper> constructorParameters) {
        this.ensureDelegateInitialized();
        delegate.addConstructor(constructorParameters);
    }

    /**
     * {@inheritDoc}
     */
    public void addEmptyConstructor() {
        this.ensureDelegateInitialized();
        delegate.addEmptyConstructor();
    }

    /**
     * {@inheritDoc}
     */
    public int newDynamicInvocationId() {
        this.ensureDelegateInitialized();
        return delegate.newDynamicInvocationId();
    }

    /**
     * {@inheritDoc}
     */
    public void loadThis(MethodVisitor visitor, CompilationState state) {
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        state.pushStack(this);
    }

    /**
     * {@inheritDoc}
     */
    public String getSourceCodeFileName() {
        return this.sourceCodeName;
    }

    /**
     * {@inheritDoc}
     */
    public String className() {
        return this.className;
    }

    /**
     * {@inheritDoc}
     */
    public String simpleName() {
        return this.simpleName;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isInnerClass() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void addConstructorWithBody(Parameter[] params, BytecodeGenerator bytecodeGenerator) {
        this.ensureDelegateInitialized();
        delegate.addConstructorWithBody(params, bytecodeGenerator);
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper superType() {
        this.ensureDelegateInitialized();
        return delegate.superType();
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends DeclaredConstructor> getUnfinishedConstructors() {
        this.ensureDelegateInitialized();
        return delegate.getUnfinishedConstructors();
    }

    /**
     * {@inheritDoc}
     */
    public List<? extends TypeWrapper> interfaces() {
        this.ensureDelegateInitialized();
        return delegate.interfaces();
    }

    /**
     * {@inheritDoc}
     */
    public boolean isInterface() {
        return 0 != (Opcodes.ACC_INTERFACE & modifiers);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isPrimitive() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor getField(String name) {
        this.ensureDelegateInitialized();
        return delegate.getField(name);
    }

    /**
     * {@inheritDoc}
     */
    public int numberOfSlots() {
        return 1;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isStatic() {
        return (this.modifiers & Opcodes.ACC_STATIC) != 0;
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addDelayedField(TypeWrapper variableType, String name, int accPublic) {
        this.ensureDelegateInitialized();
        return delegate.addDelayedField(variableType, name, accPublic);
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends FieldDescriptor> getDelayedFields() {
        this.ensureDelegateInitialized();
        return delegate.getDelayedFields();
    }

    /**
     * {@inheritDoc}
     */
    public void writeMethod(int lineNumber, String methodName, List<? extends TypeWrapper> typeParameters)
            throws CompilationFailedException {
        this.ensureDelegateInitialized();
        this.delegate.writeMethod(lineNumber, methodName, typeParameters);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isMethodImplemented(String methodName, List<? extends TypeWrapper> typeParameters) {
        this.ensureDelegateInitialized();
        return delegate.isMethodImplemented(methodName, typeParameters);
    }

}
